Welcome to get started on our project.
We're first going to work on our drink class for the server.
We're actually going to have two drink classes.
One is going to be for the client and one's going to be for the server.
The purpose of the server drink class is to create drink objects on the server, and verify any information
coming from the client for the creation and consumption of drinks.
The purpose of the client drink class is to connect events to our drink tool that gets added to our
inventory, and then do all the fun stuff with the drink text on our screen.
So let's get started with the server side of drink class and the pre-made drinks module script, so
we can go into replicated storage into our assets folder.
And what I'm going to fill out is the pre-made drinks module script, because we want to be able to
define all of the properties that we need for our drinks.
So specifically in this module script, first I'm going to define the sound service.
And that's because I want to be able to get access to all those different audio files we have inside
of the sound service.
And then from this point, I can also define some constants that we can use to create our custom drink.
So one can be a dispense message that will set for basically almost every single drink.
It'll just be a message that just says dispensing.
And we'll just do three dots.
And then we could do the same thing for let's say they input a drink and that drink doesn't exist.
Then we could just do out message and set it to out of range.
And now we can create a table for this module script.
I'm just going to call it module.
Doesn't really need to be named anything fancy.
And then I'm going to create a reference to the sounds folder and sound service and get SCP 294 sounds.
And then I'll create a table that contains all these sounds that we can pick of for dispensing and consuming
our drink.
So one section here will be for dispense sounds, and we'll set it to a table.
And the second one will be for consume sounds and set that to a table as well.
And inside of this table we can set the first dispense sound to be equal to the sounds folder.
And there should be a folder in there called GUI.
And we get the dispense sound.
And then we can go ahead and do it for the dispense sound.
And this one will be no liquid.
So for example if they get a cup of air we don't want any liquid dispensing sound.
So sounds folder GUI, dot dispense.
And I believe it's no liquid.
There it is.
And we're just going to do this for all of them.
And the last one is struggle.
And again, we're going to do the same thing for our consumption.
Sounds.
So one's going to be a good consumption sound.
So they drink and they're like hmm tasted good.
And that's going to be drink good.
Um, the next one we'll have is consume you one.
And that sounds folder dot g dot drink you one.
Oops.
Forgot to put a comma there.
We'll do it for the second one.
The spit sound.
We'll do one for when they drink and they get burned.
And then we'll do one for the drink and the cough.
And I'm not going to make a reference to the regular drink sound.
What we could do is that if they don't put any drink sounds or they don't define a drink sound for a
drink, then we'll just use the regular default drinking sound.
So after that, then we can create a table that contains, um, all of the strings that can be used
to dispense the specific drink.
So for example, if we wanted a cup of air, the player could type in air or they could type in nothing,
or they could type in emptiness, a vacuum, whatever.
We all want them to refer to the same drink.
So what I could do inside the module script is I'll create a table and I'll just call it current names.
And these are going to be all the drinks we're going to have in our shop.
So one is going to be air, and I'll just set the key to be air, and I'll set it equal to a table.
And this table is going to be an array that contains a bunch of strings, that contains all the strings
we can use for dispensing this particular drink.
So one would be air.
Another would be something like cup.
Another could be like nothing, a vacuum.
And we could be even a little funny and stick to the law.
And we could do like air l three, which means half life three.
So a cup of half life three, which doesn't exist.
And we could also do a string that's empty.
So these are all things the player could enter and get a cup of air from.
And then the next thing we could do is we could create a table and we'll call it current drinks.
And it's going to have the same keys that are in here.
So we'll have one for air.
And this is going to define all of the properties for this particular drink.
So we could create a table here.
And inside this table we can define all the things for the air drink.
So for example the color of the liquid we could set the transparency for air to one because there's
nothing for the color.
We could just set to a new color three.
That's just nothing because again, it's going to be completely transparent.
The material again, it could be nothing.
We're going to actually set defaults for this inside of our module script.
So we don't really have to worry about this too much.
We can set something like the dispense message to be equal to the the dispense message constant we set.
Consume message could be something like there is nothing in the cup and this will display on that player
screen.
For the dispense sound, we could do something like refer to the sounds that are inside of dispense
sound.
So dispense sounds and get the dispense sound.
No liquid because we're dispensing a cup of air.
Don't forget that comma.
And since it's going to be empty, there's really no point for us to define a particular, uh, consume
sound for it.
So we'll just leave that empty.
But I will set a property in here.
We'll call it is empty, and we'll set it to true.
That means this cup is going to be empty when it gets dispensed, and the player isn't going to be able
to drink it because there's nothing in the cup.
And then last but not least, we can set a name for the cup.
And the cup name here is just going to be air.
And then we could like attach a string to the beginning of the string and say cup of air later on.
But this is kind of how we can set up all of the drinks that we want to have in our game.
And then of course, at the very end of this module script, we're going to go ahead and return the
module table.
So we have access to all these nice little properties here.
All right.
Now next comes the fun part.
We're going to start working on the module script or the drink class for the server.
What I'm going to do is I'm going to duplicate this.
I'm going to get rid of all these children.
And I'm going to name this one drink class.
And we'll put some brackets and a C in here to define that.
This is the client's drink class.
So we'll fill this one out in the next lecture.
But let's go ahead and fill out this one.
And I'll rename it to bracket S to denote it as the server.
And in here we're going to need quite a few services.
One service is the Http service.
And we're going to use this to generate a custom ID for every single drink in our game, because we
want to be able to get drink objects of a specific ID from the client, and we don't want to base that
on maybe the owner of the drink, because a player could be holding multiple drinks, and we want to
get a specific drink object so we can put an ID, a custom ID that exists in each object.
And we use that using the Http service.
We'll get a reference to replicated storage, so we can get access to all the assets for our SCP.
We're going to use the run service to make sure that whatever requires this module script is running
on the server.
And you'll see what I mean in a moment.
And last but not least, we'll get a reference to the sound service.
All right.
I think we're good there for services.
Now it's time to create some variables.
So I'm going to create a variable called is server.
And it's going to store the result from the run services function is server.
So this returns a boolean telling you whether or not the context for the script is running on the client
or running on the server.
So did a server script require this module script or did a local script require this module script?
And since this module script is only going to be for the server if the client or an exporter tries to
require this module script, then we're just going to return nothing.
So we could do.
If not is server, then we'll just return an empty table and that's it.
Otherwise, from that point we can create a table to represent our class.
So I'll just call it drink.
And I'll also have a table to represent the meta table for our class.
So we uphold encapsulation.
And we'll make sure to set the underscore underscore index meta method equal to the drink meta table
table itself.
So that way we can get access to all the little neat functions in our meta table.
From this point I'm going to create a reference to our cup tool which is just script dot drink cup.
I want a reference to the request event inside of replicated storage that's going to be replicated storage
the assets folder and git request.
And we'll do the same thing for the communications event.
And then I'm going to create a table.
I'll call it Current drinks.
And this table is going to act as a cache.
So every single drink object that gets created in my game, I'm going to store inside of this current
drinks table.
That way we can access them later on.
For clients that need to request a drink object, then we're going to also refer to those two module
scripts, which is the pre-made drinks functions and pre-made drinks module scripts.
So we can have one pre-made drinks equal to require script dot pre-made drinks.
And then we could do the same for the functions.
And then to help with some randomization we're going to use later, I'll create a new random data type.
And then now for the constructor of our class I want to be able to enforce, uh, whoever is scripting
in our game to input things into the constructor, uh, like in a specific order, or showing us specifically
what the drink constructor needs.
So what I could do is I could create a custom type, I'll call it drink properties.
And we'll set it equal to a table.
And now inside of this table we want several things in order to create a drink.
First off, perhaps when we create a new drink object, we want to set an owner for it so we can have
an owner key value pair.
And we can make this optional.
So we'll just set it to player question mark.
So this may or may not get passed in the table.
We obviously want a name for our drink cup.
So we'll set that to the string.
We want to be able to access the key that this pre-made drink was made from.
So specifically here we want to be able to access this particular key, just in case.
We need to access any information like the strings in here or any of the information here down the line.
So we'll store the pre-made key in here just in case.
And then we want to define the properties for the liquid that's going to be in our cup.
So we can call it liquid properties.
And it's also going to be a table.
And this table is going to have, you know the transparency for the drink, which is a number.
It's going to have a color for the liquid which again is a color three.
And then we're going to have a material for the liquid.
And that's going to be enum dot material.
Another key value pair we need to define is the dispense message.
That will be a string.
We'll need a consume message again.
That'll be a string.
And we could also set it to a table that contains strings.
So like an array of strings.
And we could do that to perhaps make a randomly generated message for when a player consumes a drink.
So let's say you want to have more than one message for a particular drink, and you want to pick a
message at random from a table.
We can do that right here.
We'll just define it to be either a string or a table containing strings.
And then we'll need a dispense sound for this object.
And that'll be a sound instance.
And then we'll need a consume sound a sound instance.
And then last but not least, that boolean that tells us whether or not the cup will be empty when it
gets dispensed.
We'll set that to a boolean.
Now this is our custom type right here.
Now you may be wondering, well, if I require this module script and a server script, how am I going
to be able to see this type here if it doesn't belong to our drink table?
That's a good question.
And we can't set this drink properties inside of this table.
So how the heck do we do it?
Well, we can use another keyword in Lua you called export.
And that means we're exporting this custom type to be part of the table that gets returned from this
module script.
So that means if we index our drink table, we'll be able to see this particular drinks property type,
this custom type, and we'll be able to fill out all of these different key value pairs that we need
in order to use our constructor.
Now that does mean because we have all these requirements, what if someone who's scripting forgets
to, you know, include one of these properties?
Well, we can create some defaults.
So I'm going to create a table call.
It defaults and it's going to represent constants which is why I'm capitalizing it.
And in here we can set a bunch of different defaults for all of these properties.
So for example the liquid we can call it cup properties.
And the default can be like maybe the transparency will be zero.
The color will just be black.
And the material can just be, I don't know, enum dot material and we'll just do smooth plastic.
We want to have a default name in case for some reason a name never got passed, and the default name
we could just set is Drink Cup.
Um, we can set a default dispense message.
So dispense underscore message equal to.
Dispensing.
This will be the default.
Don't forget your commas.
We can have a default consumption message when a player consumes the drink, and since they didn't pass
anything, we'll just put like three dots because I don't know what to put there.
We can have a default, uh, dispense sound in case one doesn't get passed.
So that'll be sound service.
Get the sip.
Two nine, four sounds folder, the GUI and the dispense sound.
And we could do the same thing for the consumption sound in case they did not pass a consume sound.
So sound service SCP 294 sounds, and we'll just do the regular consume.
I believe it's just called drink.
Yeah.
This drink sound instance.
So these will be our defaults.
And last but not least we'll have a default for the is empty boolean.
And we'll just set the default to be false.
And then we can also define a constant for a string we can attach to the name of the drink.
And I'm going to call this drink name and set it to cup of and then a space.
So what my plan is, is from all these pre-made drinks it's going to create a bunch of these objects.
When I play or requests it.
Right.
And we want to attach this string to the name of the drink.
So in this case it would be air.
So we would get a cup of air in our inventory.
All right.
So now it's on to creating some private functions for our class one private function.
That's going to be very important for us.
And I'm going to call it check arg for type.
And the purpose of this function is going to check a type to see if it is equal to another type.
So if we have functions in here or we have our constructor or those events we're listening to, we want
to make sure that whatever value is being passed through those functions, whatever value is being passed
to the events, are of the correct type or of the expected type.
And if they aren't, then we can create an error in the script.
So the first argument to this function could be just the argument we're looking at.
And it could be anything.
So we'll set it to any.
And we can say the required type is going to be a string.
Because when we use the type of functions it's going to return a string back to us.
So when we get this arg passed to this function, we can check if the type of this arg is equal to the
required type.
So if the arg is what we expect, then we can just return and end.
We don't need to error.
However, let's say the argument we passed is not, you know, one of the primitive data types, but
it's one of perhaps maybe a custom type in Roblox.
Like maybe it's a part or it's a tool, and we want to be able to make sure that whatever is being passed
is of the class of tool, or of the class of part and so on.
So instead, what we could do is we could create another if statement, and we could check if the type
of this argument is equal to the custom data type of instance.
Because when we use the type of function on parts, on unions, on perhaps these remote events, remote
functions, whatever it is, it's not going to tell us it's a remote event or it's a remote function,
it's just going to tell us it's an instance, and if it's an instance, then we need to check the class
of that instance.
So what we could do is that if the type of this argument is an instance, then we can check to see if
the argument is of a particular class.
Using the is a function.
And we can pass the required type.
And if it is, then we'll just return.
So, for example, if the argument we get is a part and we're expecting a part, it'll check to see
that this part is of the class part and we're good.
Otherwise, if the argument we pass does not match the required type or the type we expect, then we
can create an error.
So I'll create an error message.
And it'll be equal to a string that just says something like expected type will put that percentage
s again, which is representing our directive here, which is going to be a string.
And we got type percentage s.
And a cool thing we can do with the string format function is we don't have to do string dot format.
Instead we could actually wrap this string with parentheses.
And we can call the format function directly on it using these two colons.
And in here we can just pass the strings that we want to replace these two directives.
So in this case that would be the required type.
We expected this required type.
But instead we got the type which is going to be type of our argument.
So it's formatting the string.
Let's say the required type is part.
And the type of this argument for some reason is I don't know, a boolean for some reason.
Then we could say expected type part got type boolean, something like that.
Whatever.
And then we can use the error function and just pass our error message to halt the thread of execution.
Cool.
Another function we'll need is for destroying a drink object once it's been consumed.
And we'll pass self to this and we'll fill this out later.
Um, we'll have a function for applying all of the properties of the liquid onto our cup tool.
So I'll just call it apply properties and we'll get an instance in here.
Say it's an instance and we'll get the properties for it.
And this one's pretty easy.
What we could just do is loop through every single property and value in pairs.
Properties.
And what we could do is just set the instance.
Property.
Equal to this value.
So if it's a part and we have properties like the size or whatever, we'll set the size to that value.
Pretty simple.
We will need a function to listen for okay.
So let's say we create a drink object.
And the person who has this drink object in their inventory, let's say they die and they didn't consume
the drink object.
Well that means we're going to have a table taking up space and the player doesn't have the tool anymore.
So we want to be able to listen for when that player dies and when they die.
We can destroy this drink object and free up memory.
So we can call this function on owner death.
We'll pass self to it because again, self is going to represent the object or the drink object we need
to destroy.
And we'll fill this out in a little bit.
Um.
Oh.
Another function we'll need is I plan on.
Okay.
So I'm going to create a name in here.
Actually I'll do it right now.
We'll copy this and we're going to call this random.
So let's say a player wants to get a random drink.
They type in random or they type in randomness.
We want to get them a random drink in the game.
So we can create a function for picking a random drink.
We can call it get random drink name.
And what we could do is we could create a table, we'll call it drinks.
And we're just going to insert every possible drink name that is inside of the table, pre-made drinks.
And we'll get the current drinks.
So any drinks that are defined in this table here we're going to get the key for that particular drink.
And we're going to put it in the drinks table.
So we'll just do table.insert drinks and we'll do the drink name or the drink key whatever you'd like
to call it.
And then what we could do is we could return this table, but we're going to pick a random drink out
of it.
So we could do RNG mixed integer one to the max number of values inside of this drinks table.
So if there's 50 drinks we insert 50 names in here.
And we pick a random name out of those 50 drinks.
And I think one more thing I want to do is I want to have a function specifically for creating drink
objects.
So I'll call it create drink object.
And to this function we're going to pass a player who's going to act as the owner for the drink object.
And we're also going to pass the name of this drink object or the key of the drink object.
So let's say a player requests from the client to create a particular drink object that has been pre-made
here.
So let's say they want a cup of air.
Then we'll use this function.
We'll pass that player to the function and we'll pass the drink name to the function.
And then from there we can go and create the drink using all the information in here.
And of course that means we're going to have to set up our constructor.
So let's go ahead and do that.
So down here in this public function section inside of our drink table, we're going to create a function
dot new our constructor.
And of course this constructor requires us to pass some arguments to it.
And that's going to be drink args.
And it has to be of the type our custom drink properties table.
Very simple.
Now we're going to specifically use this constructor for creating drinks that exist in our premade drinks
module script.
So if we have a bunch of different drinks in here like air, honey, soda, whatever, then that means
we're going to have to access the key for that particular drink.
So that means when we required up here to have a pre-made key to be stored in our drink properties,
we want to make sure that it actually exists when this constructor gets called.
So we can use the assert function.
And then do drink args dot premade key if premade key doesn't exist.
If this value is false or nil, then this is going to error and we can do an error message like drink
dot new requires original key for premade drinks.
So if they did not supply us a key then we're not going to do anything.
We're not going to create nothing.
Otherwise if we're good to go then we're going to create a new table called self.
And we're going to set the meta table of an empty table to the drink, empty the drink meta table.
And then from this point we can set a bunch of properties in here.
For example inside of this object maybe I want to have a cup property that's going to represent the
tool.
And we can do cup tool and clone it and create a new tool for this particular object.
We can set the name for this object equal to drink args dot name.
And if for some reason it doesn't exist, then we can use our default, which is or defaults dot name.
We'll do the same thing for the key.
So premade key is equal to drink args dot premade key.
Self dot.
The properties for the liquid is equal to drink args dot liquid properties.
And if it doesn't exist then we'll do the defaults.
Cut properties.
And again, we'll just do this for every single one.
So is empty is equal to drink args.
That is empty.
Or defaults.
Dot is empty, so if it's true, we'll set it to true.
If it's false, then it'll just stay false.
Um, we're going to have a property in here that's going to act as a debounce for this particular object.
So we'll call it effects debounce and we'll set it to false.
And this is going to be for the drink functions.
So let's say this drink had a particular function we needed to execute on the server or on the client.
Then we want to make sure we don't execute it multiple times by keeping track of this debounce.
And then some other things we could set is like the dispense message, just set it equal to drink args
dot dispense message or defaults, not debug defaults dot dispense message.
And again we're just going to keep going on and on.
And can see how having a custom type makes it really easy for us to see the particular different properties
we need for our drink arguments, so we can just hit dot, index it, and we can see all the properties
we have for our particular drink.
So it's again pretty useful.
So I'll have a consume sound or the default.
And then last but not least, we need to have a custom ID for our new drink object.
So we'll call it.id and it's going to be equal to the Http service.
And there's a function in there called generate g uid.
And it generates a uuid g uid random string.
So basically it generates a string that looks like this.
It's a bunch of numbers letters and dashes.
And we can use this to, you know, find a particular drink with this ID.
And these IDs are completely random.
The chance of an object having the same ID as another is basically zero, so you don't have to worry
about it.
So now that we have our custom ID set for our drink, I want to be able to set the tool to have an attribute
that's equal to the id.
So on the cup tool itself we're going to set an attribute called drink ID and it's going to be equal
to self.id.
So that way when a player gets a drink cup and it gets put in their inventory, the player can go,
hey, I've got this new drink cup.
I want to request the drink object with this drink ID so I can get access to, you know, like the sounds
and the messages that I need to display on the screen.
And then we could also set the name for the cup.
So cup dot name, we'll set it equal to I guess we'll just do self dot name for now, because what we'll
do is we'll set the drink args name to be concatenated with the string up here later.
So we'll set the cup name equal to the name.
And then we also need to apply all those properties to our particular cup tool.
So we could call that apply properties function on the self dot cup.
And inside of our tool there's a part that represents the liquid.
So dot liquid.
And we need to apply all of these self dot liquid properties.
So now that we've got basically everything we needed set up inside of our drink object, we can insert
it into the current drinks table.
So that way we can access it later on or destroy it if we need to.
And we can check to see if drink args not owner was passed.
So do we get an owner pass to this function?
If we did then we can set the owner for this particular drink.
And actually we'll create a function for that in a little bit as well.
So I'm going to leave this blank for now.
But after we have set all of this stuff up for our object or our table, then we can just return it
at the very end.
So now we've got our custom drink object that contains the tool, the name, the effects, the sounds,
the messages and the custom ID and all that good stuff.
So now let's create some other functions.
I want to exist inside of my drink table.
So one function I want is to get all of the drinks that are in my game.
So we'll call it get drinks.
And all it's going to do is return the current drinks.
I also want to have a function for getting a particular drink object based off the ID that is passed
to the function, so we can call this drink.
Get object from ID, and we need to pass an ID to this function, which is a string.
And for this function in particular we can check the arg.
For type this id we want to make sure it is of the type string.
Otherwise if it isn't, we're just going to error.
Otherwise we can just loop through every single drink object that is inside of our current drinks table.
And what we could do is we could check if this drink this particular drink object.id is equal to the
ID passed to this function.
And if it is, we'll return this drink and that's it.
If we didn't find anything then this function will return nothing.
So again this is important.
Why we have this ID here.
That way the client can get a particular drink based on the id.
Now let's create some functions for the drink meta table or specifically for the drink object itself,
not part of the class.
One function we want to have for the drink meta table is to be able to set the owner of our drink.
And this owner is going to be, of course, a player.
And again we can use that check arg for type function.
We can check this player or this owner that gets passed to this function to make sure it is of the type
player or this instances of the class player.
And we'll just pass player here.
And if it is, we can do self.
Dot owner is equal to the owner.
And remember self is referring to the object that gets created from our constructor.
And there is an owner property in there I believe.
Um, or actually we didn't set it yet.
So I'll put an else statement here.
We'll just do self dot owner is equal to nil.
But basically we'll set an owner property in here.
And now that means because this cup now has an owner we want to be able to listen to when this owner
dies.
So let's say they have the drink in their inventory.
They never drink it but they die.
We want to be able to delete that cup object so we can free up memory.
So we can do self dot owner get their character and get the humanoid in their character and listen to
the died event.
So when that player dies we can connect a lambda function to it.
And we'll call that on owner death function I talked about earlier and pass this, uh, drink object
in particular to this function.
And the last but not least, we need to set the parent of this particular cup tool to that owner or
that player's backpack.
So owner backpack.
Now we're going to need a function for consuming the drink.
So drink meta table consume.
And this is going to be on the server only I plan to have a consume function on the client and on the
server.
So when the player consumes it on their end, they're going to apply any of the client side effects
that may exist in the pre-made drink functions, uh, module script.
And of course, they're also going to display the GUI message on their screen.
And all I want the server to do is set the transparency of the liquid to be invisible, so it makes
it look like we drank out of the cup.
So self dot cup dot liquid dot transparency equal to one.
That's all I want this function to do.
Last but not least, we need to have a function for all of our drink objects to be able to destroy them.
So we can call this drink empty destroy.
We want to be able to destroy this drink object.
And since this function is going to be called from the server, we need to let the player know that
they need to destroy that object on their end as well, because there's going to be two separate objects,
one that exists on the server and one that exists on the client.
So we need the client to destroy their drink object as well.
And that is what the communication event is for.
So comms event fire client.
And we're going to fire to the self dot owner.
And we'll tell them to destroy drink object.
This is going to be the action we want them to do.
Which drink object we want them to destroy?
Well, we can tell them by using the self.id.
So we're telling the client, hey, look for an object on your end that has this ID and we want you
to destroy it.
And then from that point, we also need to destroy the drink object on the server as well.
So we can call that destroy drink object function and pass the object to it.
And then I guess I can have this function return true to make sure that, you know, the object got
destroyed.
So let's go ahead and fill out this destroyed drink object function.
We'll scroll back up here to the private functions that exist in this class.
So what I want to do when I want to destroy a particular drink object, well first I need to destroy
the tool.
So self dot cup will destroy that tool.
And then what we need to do is we need to find this particular object inside of our current drinks table.
So we'll get the index by using the table dot find function.
So inside of the current drinks table we're going to look for this particular object.
And it's going to tell us at what index it resides.
And that means we can remove it from our table.
So current drinks remove it at that index.
And then we need to set the meta table for this object to nil.
So we're basically getting rid of the meta table.
And then we're going to destroy the table by clearing all the contents using the table dot clear function.
And it clears out every single property key value pair whatever we had in the table.
So all this stuff right here, it all gets destroyed.
And then we can just basically, I guess, set self to nil.
But since it's going to be an empty table, it doesn't really matter that much.
And this is basically how we destroy our drink object.
And we're going to call this function again from the destroy function.
That's part of the drink meta table.
And actually what we could do here, just in case this particular drink doesn't have an owner.
If self dot owner.
So if there's an owner then we'll fire the event.
Otherwise we'll just destroy it on the server end.
Now, of course, that means we're going to need some event handlers specifically for our communication
event and for our request remote function.
So let's fill out the request event or the remote function first.
So we're going to access the on server invoke property and set it equal to a Lambda function.
Now, what I want the client to pass to the server is the action the client wants the server to do,
and any other arguments that needs to be supplied with that action.
So first off, the on server invoke when it gets called will automatically pass the player.
So we get the player, we get the action they want us to do, which should be a string, and we just
get some extra arguments or a table containing all the other arguments we want to do for this particular
action.
And of course, we're going to have to verify the information from the client so we can check ARG for
type of action to make sure it's a string.
We can check arg for type of the arguments, and we'll make sure it's a table containing, you know,
arguments and whatnot.
And once we verify that's all good, then we can do if action is equal to request drink object.
Then what we could do is we could get that object.
From its ID, so when the player or the client wants to request a drink object because they got a tool
set in their inventory, they're going to go ahead and request it.
And that'll be in the args table.
And we'll say that's going to be the first argument.
So args one is the ID.
So we're going to get the drink object from this ID.
If we didn't get an object, then we'll just return a false to the player and we'll give them a message
like drink object with ID.
We could do dot dot dot args one.
Does not exist.
So whatever drink object they requested with this ID doesn't exist.
The next thing we need to do is we need to make sure or verify that they're the owner of this drink
object.
So if object dot owner.
Is not equal to this particular player.
Then again we're going to return false and we're going to tell the client you do not own this object.
They can't have it.
Otherwise, if all checks out good, we can return true to the client to say everything went well and
we can return that object back to them.
Now you need to understand there is a limitation with, uh, remote events and remote functions.
And when we pass tables to a client or a client passes a table to the server, any meta table that is
attached to that particular object gets destroyed.
So you won't be able to have an object with this meta table attached to it.
And for our purposes, that's going to be totally fine, because what we're going to do on the client
is that when they get this table, which is just going to contain all these properties, we're going
to attach the meta table that exists within the drink class on the client instead.
And that meta table contains all the client related functions.
So it works out well for us.
We don't have to worry about the meta table being lost.
Now some other actions I want to put in here that the client should be able to do is to check to see
if a drink exists.
So let's say they're in the GUI and they're punching in the name of a drink.
And that drink for some reason doesn't exist.
Well, they need to check to see if it exists in the first place.
So that's what they can use this remote function for.
They can request an action like check for drink.
And what we'll do is we're going to loop through every single, uh, key and possible names that is
inside of the table.
Pre-made drinks dot current names or not current drinks, current names.
So let's say they input a name like vacuum.
What we're going to loop through every single key value pair in here we're going to see if a string
of vacuum exists.
So it'll look through here and it'll say hey vacuum exists.
We'll get that key and we'll tell you, hey, this drink exists and all will be fine.
So we're going to loop through all of those and we're going to check.
If not table dot find inside of all possible names or possible names.
ARGs and we'll do args one.
So again args one is the name for the drink.
So if we couldn't find this drink in this first iteration of the loop, then we're just going to continue
looping.
Otherwise, if we do find it, we can return true.
We can return to them the key for this particular drink.
And then we can also return to them.
Pre-made drinks dot current drinks and get the.
Properties for this particular drink.
So we're telling the client, hey, this drink exists.
Here is all of the properties for the drink and the key as well.
So you can, you know, go ahead and request to create a new drink.
Another action I want the client to be able to do.
Is to create a new drink object and get it.
So they're requesting it from the client.
So we'll call it create and get drink object.
And the only purpose of this is to return whatever gets returned from the create drink object function,
and we want to pass the player and args one and args one is going to of course.
Represent the key, so args one is the key for the pre-made drink.
And then one more action we want to do is to destroy a drink object.
So let's say the client on their end wants to destroy a particular drink object they have.
Then what we could do is we could look for this object.
So args one can be the id.
So is the id of the object.
We'll create a variable and we'll call the get object from id function.
That's part of our class and pass args one.
If there is no object, then we're going to return false.
Object does not exist.
If the owner of this object is not equal to the player, then we're going to return false as well,
because we don't want exploiters to be able to destroy other people's drinks.
So we could say you do not own this object.
Otherwise, if everything checks out, we can destroy the drink object on the server using the destroy
drink object function, and we'll return true to say, hey, we successfully destroyed the object.
Otherwise, let's say the client passed an action that didn't match any of these that I defined.
Here.
We can just return false at the very end and just say requested action did not match possible actions.
And that's all the client needs to know from this function.
Now let's go ahead and listen to the comms event.
So on server event.
When this remote event gets fired from the client, it's going to pass the player that fired the remote.
Um, again, we'll want to have an action.
So string and we'll have arguments from the client.
And again we'll check the arguments action to make sure it's a string.
And we'll make sure the argument they passed is a table containing some arguments.
So we can have an action for, let's say, the player verified that a drink existed.
Then they want to go ahead and create a drink object and get that drink object in their inventory.
So we can have an action called create drink object.
And we can create the string object.
Passed to the player.
And args, one is going to represent the key for this particular drink object.
So args one is the key for the pre-made drink.
We're going to have another action.
Let's say this action is, oh, I know what to do.
Drink effects.
So let's say we have a function for this drink and they consume the drink.
And let's say this drink also had some side effects.
So like maybe they drink it and they get health reduced or they change color or whatever the case may
be.
We want it to be visible to other players, so we have to do it on the server as well.
So.
We can check to see if they have a particular object they're requesting.
So args one can be the id, so is the ID for the drink.
Because we need to verify that this drink actually has a function.
So we can do drink get object from id and we'll pass args one.
If there is no objects, then we'll just return.
Um, then we can check to see if a player actually has this tool in their inventory.
So if player.
We're going to find first child inside of their backpack.
To see if it's equal to Objectname.
If it doesn't exist in their backpack, then we'll check the character.
So player dot character find first child.
Objectname.
If they don't have this tool for some reason.
Again, we'll just go into return.
We don't want to apply any effects.
Otherwise if everything checks out then we need to check the object dot effects Debounce.
Remember that effects Debounce we made.
We don't want to apply a server side effects more than once, so if it's already set to true then we're
going to return.
Otherwise we're going to set it to true so we can only execute it once.
And then from this point we can access that drink functions module.
And what I could do inside of the pre-made drink functions is I'll just fill this out real quick.
We can have a section in the module scripts.
We'll call it something like, uh, current functions.
It's going to be a table and it's going to store the key for a particular drink.
So let's say air.
And we can set it equal to, I'd say, a table.
We can create a key value pair in this table we'll call it handler.
And it's going to be equal to a function.
And what we want to do is we want to execute this function for this particular drink.
If it has server side effects or if it has client side effects, then the client will do that on their
end.
But if it does have server side effects, we want the server to execute as well.
So we can access the current functions.
Current functions.
This table here, and I'll make sure to return it at the end here to make this more clear.
So we get the current functions table, and we check to see if the key exists for this particular drink.
So object dot premade key.
It's very important why we store this key to verify if there's any functions.
If there is then we can call the handler function that exists within the table attached to it.
So.
If this key exists in our current functions table.
So there's a function for this drink.
Then we can just copy this.
And then call the handler function.
And what we could pass to the handler function is the player.
That requested the server side effects to be applied, as well as the object itself to keep track of
maybe anything else we want to do in the function.
So that means that any function that gets called here will have a player object, and it will have the
object itself or the drink object, and we'll just call it drink obj drink object.
All right, hang in there.
We're almost done with this server side class.
Another action I want the client to be able to request through this event is to consume the drink.
So when they consume the drink on their end, we also want to consume the drink on the server and make
the liquid disappear.
So again.
ARGs.
One is the ID for the drink.
Local object.
We're going to again use the get object from id function and pass args one.
If not object, then we're just going to return.
If it does exist, we need to verify that they're the owner.
If not, object dot owner is equal to this player, then we'll return.
Otherwise we can consume the drink.
And actually this not is redundant.
We actually just do.
Tilde equals.
So if the owner does not equal to the player, then we return as well.
And I'll make sure to return this drink table at the end of the module script.
But there is a couple more private functions we need to fill out.
Yes, these two right here.
So what do we want to happen when the player or the owner of this particular object dies and they didn't
consume the drink?
Well, what we want to happen when the player dies is we want to destroy the drink object, right?
So we can just do self destroy.
We destroy the drink object on the server.
And again when we call this function, let me scroll down to it.
It'll also tell the client to destroy it on their end as well.
Now let's go ahead and fill out the create drink object function which we use down here and right here.
So when the player wants to create a pre-made drink that exists inside of this table, using all of
these properties, we're going to use this function to fill out our custom drink properties type here
that needs to be passed to our constructor.
So.
What we're going to do is we're going to get the information for this drink, which exists in pre-made
drinks.
Current drinks.
And we get the drink name or the key for this particular drink.
Now, if this drink name is random or the key is random, that means we want to get a random drink instead.
So we could we could do.
Name is equal to get random drink name.
Remember this function we made?
We loop through all of the current drinks and get a random name or a random key.
And from that point we could just set drink info equal to pre-made drinks dot current drinks.
With this particular name.
So we're just picking a random drink.
Otherwise, if there is no drink info, basically the string they pass doesn't exist, then we're just
going to return and we just do return false.
Specifically for our.
On server invoke function here.
If we ever need to use it, we'll just return false.
Otherwise.
If all checks out, we need to create a name for the tool.
So it'll be called tool name.
It's going to be equal to our drink underscore name which is cup of.
And we're going to concatenate it with the drink info.
Dot.
Um, I believe it is cup name, right?
Yeah.
Cup name.
So if we get past air, it's going to be cup of Air.
Now, the next thing I want to do is I want to make sure a player doesn't get duplicate drinks.
So, for example, I don't want them to have more of one drink of a particular type.
So I don't want them to have more than one cup of air, or more of one cup of juice or whatever it is.
So we can check to see if the player already has this drink by again doing player dot backpack find
first child.
We're going to check to see if this tool exists in their backpack, or if this tool exists on their
character, meaning they have it equipped.
If they do have this tool.
And we want to make sure it is a tool.
So they have this particular tool on them.
Then we're going to return false.
And we don't want any duplicates.
No duplicate drinks.
Otherwise.
If everything checks out, then we can create a new table.
I'll call it drink args.
And we'll set it to the type drink properties, and we'll set it equal to an empty table.
And from this point inside of drink args we can set the thing like owner equal to this player.
We can set the name equal to our tool name that we just made.
We can set pre-made key equal to this drink name or aka the key that belongs to the drink this thing.
Will have the properties.
So liquid properties equal to a table transparency is equal to the drink info dot transparency.
The color is equal to drink info dot color.
And the material is equal to drink info dot material.
So we get the liquid properties set up.
Drink args dot consume message is going to be equal to the drink info dot consume message.
Again, if it's empty we'll resort to defaults that we set in here.
Drink args dot dispense message.
Again.
You see, they're all popping up, so it makes it very easy to fill all this out.
Um.
Drink args will set the is empty.
Equal to the drink info dot is empty and then we could do drink args dot dispense sound equal to drink
info.
Again I believe it's called dispense sound and drink args dot consume sound equal to drink info dot
consume sound.
So again we're just setting up this table to match all of the different properties that we expect for
this particular custom type once we got that all set up.
Then we can return true from this function, and we can return whatever gets returned from our constructor.
So we pass the drink args.
And actually, since we're checking for random here, what I want to make sure to do before we finish
this up is I want to go back to our on server invoke function here.
And when a player wants to check for a drink, if they type in random, then I want to give them a random
drink.
So what I could do in here is that when they're checking for a drinks existence, if they type in random
or randomness, I want to check to see if that name belongs to the random drink that we've created in
here.
See if it belongs to any of these strings inside of this array.
So what we could do is we could create an if statement and do table dot find inside of pre-made drinks,
dot current uh names and get the random table.
And we can check to see if this particular string exists in that table.
If it does, then we can generate a new name by using our get random drink name function, and then
we can just return true.
This new name or key, and then return all the information that goes along with that key inside the
pre-made drinks.
Current drinks.
And we use that name or key.
So that way if they type in something like randomness or random, we'll create a random drink for them
to be able to dispense and play all the drink sounds and stuff for that particular drink.
Oh, and one more thing before I set you loose.
Remember when we left this if statement blank here?
We can now actually use the set owner function in here.
And we can, you know, pass the drink args dot owner if it exists.
So that way, you know, when we're creating a new drink object we can set the owner for the string
object.
And you know, parent the cup to their backpack and connect that event to it.
So make sure you insert this function call here inside of our if statement in the constructor.
And one more thing we need to fix here is that I accidentally set consume message twice down here.
This should actually be consume sound.
So make sure you fix that real quick.
It'll be fixed in the script that's attached to the lecture.
Otherwise, I think we have all the functionality we needed set up here inside of the server.
So that now means we're going to have to move on to creating the module script or the class on the client,
as well as filling out or finish filling out our pre-made drinks functions.
And we're also going to have to fill out the local script that exists within our 294 GUI.
And then we're also going to have to fill out our SCP 294 handler server script.
So this script is going to be attached to the lecture.
I'll put some extra comments in there to make it more clear as to what's going on in our server side
class.
Otherwise, I'll see you in the next lecture.